// sound.h
// Created by yan on 2020/6/11.
// 声音模块。

#ifndef C_PROJECT_SOUND_H
#define C_PROJECT_SOUND_H


#include <windows.h>
#include <stdint.h>
#include <stdbool.h>
#include "string.h"
#include "message.h"
#include "utils.h"


static HWAVEOUT audioId;                   // 声卡句柄。
static _Bool status = false;               // 声卡状态，是否打开。
static unsigned char *levelBuffer = NULL;  // 音频可视化缓存数据。


typedef struct SOUND_INFO { // 储存当前播放音乐的信息。
    double totalTime;       // 音乐总时间，单位：秒。
    double currentTime;     // 当前已播放的时间，单位：秒。
    int totalByteNum;       // 音乐总大小，单位：字节。
    int BitsPerSample;      // 采样位数。
    int channel;            // 声道。
    double level;           // 当前音平大小，范围 0 到 1。
}SOUND_INFO;
static SOUND_INFO thisInfo;

// 获取播放信息，使用了 Windows API。
// 文档链接 https://docs.microsoft.com/en-us/windows/win32/api/mmeapi/nf-mmeapi-waveoutgetposition
void soundInfo(SOUND_INFO *info) {
    if (not status) return;
    MMTIME mmTime;
    mmTime.wType = TIME_BYTES;
    waveOutGetPosition(audioId, &mmTime, sizeof(mmTime));
    thisInfo.currentTime = (double) mmTime.u.cb / thisInfo.totalByteNum * thisInfo.totalTime;

    mmTime.wType = TIME_SAMPLES;
    waveOutGetPosition(audioId, &mmTime, sizeof(mmTime));
    unsigned char level;
//    memcpy(&level, buffer + (mmTime.u.sample * thisInfo.BitsPerSample / 8 * thisInfo.channel), 1);
    level = levelBuffer[mmTime.u.sample];
    thisInfo.level = (double) level / 255;
    *info = thisInfo;
}

void soundReset() {
    waveOutReset(audioId);
}

void soundClose() {
    status = false;
    soundReset();
    waveOutClose(audioId);
}

void soundPause() {
    waveOutPause(audioId);
}

void soundRestart() {
    waveOutRestart(audioId);
}

// 回调函数。
void CALLBACK waveOutProc(
        HWAVEOUT hwo,
        UINT uMsg,
        DWORD_PTR dwInstance,
        DWORD_PTR dwParam1,
        DWORD_PTR dwParam2) {
    if (uMsg == WOM_DONE) {
        if (status){
            MSG_MY newMsg = {EVENT_MY_MUSIC_DONE, 0};
            postMessage_My(&newMsg);
        }
    } else if (uMsg == WOM_OPEN) {
        MSG_MY newMsg = {EVENT_MY_MUSIC_START, 0};
        postMessage_My(&newMsg);
    }
}

// 打开音频输出。
void outOpen(const WAVEFORMATEX *format) {
    waveOutOpen(
            &audioId,
            WAVE_MAPPER,
            format,
            (DWORD_PTR) waveOutProc,
            0,
            CALLBACK_FUNCTION);
}

// 播放音乐函数，format 为输入音频格式，data 为输入音频缓存地址，length 为输入音频缓存长度。
// waveOutWrite 文档链接 https://docs.microsoft.com/en-us/windows/win32/api/mmeapi/nf-mmeapi-waveoutwrite 。
void soundPlay(const WAVEFORMATEX *format, const unsigned char *data, uint32_t length) {
    if (not status) {
        outOpen(format);
    } else {
        soundClose();
        outOpen(format);
    }

    // 这里是复制一份缓存数据，不然的话当原缓存数据（也就是 data 指向的内存）被释放，程序会崩溃。具体原因不明。
    static unsigned char *buffer = NULL;       // 复制的音频缓存数据。
    free((void *)buffer);
    buffer = (unsigned char *) malloc(length);
    memcpy((void *)buffer, data, length);

    thisInfo.totalTime = (double) length / format->nAvgBytesPerSec;
    thisInfo.totalByteNum = length;
    thisInfo.BitsPerSample = format->wBitsPerSample;
    thisInfo.channel = format->nChannels;
    levelBuffer = (unsigned char *) malloc(length);

//    计算音频可视化所需的数据。
//    for (uint32_t i = 0, j = 0; i < length; i += format->wBitsPerSample / 8 * format->nChannels, j++) {
//        uint32_t tmpLen = (int) format->nAvgBytesPerSec / 100 * format->wBitsPerSample / 8;
//        if (i + tmpLen < length) {
//            unsigned char max = 0;
//            for (unsigned char *k = buffer + i; k != buffer + i + tmpLen; k += format->wBitsPerSample / 8 * format->nChannels) {
//                if (abs((int) *k -128) > max) max = abs((int) *k - 128);
//            }
//            levelBuffer[j] = max * 2;
//        } else {
//            levelBuffer[j] = 0u;
//        }
//    }

    // 单调队列，计算音频可视化所需的数据。
    uint32_t sampleSize = format->wBitsPerSample / 8 * format->nChannels;
    uint32_t tmpLen = format->nAvgBytesPerSec / 100;
    Queue_UINT32 tmpQue;
    Queue_UINT32_init(&tmpQue);

    int offset;
    if (format->wBitsPerSample == 8) {
        offset = 0;
    } else if (format->wBitsPerSample == 16) {
        offset = 1;
    } else if (format->wBitsPerSample == 24) {
        offset = 2;
    } else if (format->wBitsPerSample == 32) {
        offset = 3;
    }

    for (uint32_t i = 0, j = 0; i < length; i += sampleSize) {
        while (not Queue_UINT32_empty(&tmpQue) and (i >= tmpLen and Queue_UINT32_front(&tmpQue) <= i - tmpLen)) {
            Queue_UINT32_popFront(&tmpQue, NULL);
        }
        while (not Queue_UINT32_empty(&tmpQue) and abs((int) buffer[Queue_UINT32_back(&tmpQue) + offset] - 128) < abs((int) buffer[i + offset] - 128)) {
            Queue_UINT32_popBack(&tmpQue, NULL);
        }
        Queue_UINT32_push(&tmpQue, i);
        if (i >= tmpLen - 1) {
            levelBuffer[j] = abs((int) buffer[Queue_UINT32_front(&tmpQue) + offset] - 128) * 2;
            j++;
        } else {
            levelBuffer[j] = 0u;
        }
    }

    Queue_UINT32_destroy(&tmpQue);

    // waveOutWrite() 函数会自动开一个新的线程执行。
    // 虽然 waveOutWrite() 会把 header 拷贝一份，但是在拷贝之前，主线程就会退出 waveOutWrite() 函数，然后退出 play() 函数。
    // 如果 header 不是静态变量，在主进程退出 play() 函数后 header 就会被释放，进而导致 waveOutWrite() 函数不能正常工作。
    static WAVEHDR header;
    header.lpData = (LPSTR) buffer;
    header.dwBufferLength = length;
    header.dwUser = 0;
    header.dwLoops = 0;
    header.dwFlags = 0;
    waveOutPrepareHeader(audioId, &header, sizeof(header));
    waveOutWrite(audioId, &header, sizeof(header));
    status = true;
}


#endif //C_PROJECT_SOUND_H
